package net.lulihu.common_util.jdbc.template;

import lombok.extern.slf4j.Slf4j;
import net.lulihu.ObjectKit.BeanKit;
import net.lulihu.ObjectKit.StrKit;
import net.lulihu.common_util.jdbc.annotation.Column;
import net.lulihu.common_util.jdbc.annotation.PkId;
import net.lulihu.common_util.jdbc.annotation.Table;
import net.lulihu.common_util.jdbc.model.SelectAnnotationDescriptor;
import net.lulihu.common_util.jdbc.model.TemplateParam;
import net.lulihu.exception.ToolBoxException;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 查询语句模板生成对象
 * <p>
 * key-> 对象属性名称 ,value 属性注解描述
 */
@Slf4j
public class SelectSqlTemplate extends SqlTemplate<String, SelectAnnotationDescriptor> {

    /**
     * 查询sql模板
     */
    private static final String SELECT_SQL_TEMPLATE = "SELECT {} FROM `{}` WHERE {}";

    /**
     * 根据主键获取指定字段值模板
     */
    private static final String GETTER_BY_ID_SQL_TEMPLATE = "SELECT {} FROM `{}` WHERE {} = ?";

    private static final String COMMA = StrKit.COMMA + " ";
    private static final int COMMA_LENGTH = COMMA.length();

    private static final String CONDITIONAL_COMMA = " AND ";
    private static final int CONDITIONAL_COMMA_LENGTH = CONDITIONAL_COMMA.length();

    private static final String ANTI_SINGLE_QUOTE = "`";
    private static final String CONDITIONAL = "= ?";

    /**
     * 根据多列查询所有字段值
     *
     * @param beanClass         对象类型
     * @param conditionalFields 条件字段
     * @param values            条件值
     * @return TemplateParam
     */
    public TemplateParam getterSqlTemplate(Class<?> beanClass, String[] conditionalFields, Object[] values) {
        Table table = getTable(beanClass);
        // 查询条件参数
        String conditionalParams = getMultipleFieldConditionalParams(beanClass, conditionalFields);
        // 所有字段映射
        String columns = selectAllColumns(beanClass);
        // 生成模板
        String sqlTemplate = StrKit.format(SELECT_SQL_TEMPLATE, columns, table.value(), conditionalParams);
        return new TemplateParam(sqlTemplate, values, beanClass);
    }

    public <T> TemplateParam getSingleColumnSqlTemplate(Class<T> beanClass, String fieldName, String[] conditionalFields, Object[] values) {
        Table table = getTable(beanClass);
        // 查询参数映射
        SelectAnnotationDescriptor descriptor = getFieldDescriptor(beanClass, fieldName);
        // 查询条件参数
        String conditionalParams = getMultipleFieldConditionalParams(beanClass, conditionalFields);
        // 生成模板
        String sqlTemplate = StrKit.format(SELECT_SQL_TEMPLATE, descriptor.getColumnName(), table.value(), conditionalParams);
        return new TemplateParam(sqlTemplate, values, descriptor.getFieldClass());
    }

    /**
     * 获取多列属性模板
     *
     * @param beanClass         对象类型
     * @param fieldNames        多个属性值
     * @param conditionalFields 条件字段
     * @param values            条件值
     * @return TemplateParam
     */
    public TemplateParam getColumnsSqlTemplate(Class<?> beanClass, String[] fieldNames, String[] conditionalFields, Object[] values) {
        Table table = getTable(beanClass);
        // 查询参数映射
        String resultParams = getMultipleFieldResultParams(beanClass, fieldNames);
        // 查询条件参数
        String conditionalParams = getMultipleFieldConditionalParams(beanClass, conditionalFields);
        // 生成模板
        String sqlTemplate = StrKit.format(SELECT_SQL_TEMPLATE, resultParams, table.value(), conditionalParams);
        return new TemplateParam(sqlTemplate, values, beanClass);
    }

    /**
     * 根据主键id获取多个列的值 sql模板
     *
     * @param beanClass  对象类型
     * @param fieldNames 多个属性值
     * @param pkId       主键值
     * @return TemplateParam
     */
    public TemplateParam getColumnsByIdSqlTemplate(Class<?> beanClass, String[] fieldNames, Object pkId) {
        Table table = getTable(beanClass);
        // 查询参数映射
        String resultParams = getMultipleFieldResultParams(beanClass, fieldNames);
        // 主键名称
        String primaryName = getPrimaryDescriptor(beanClass).getColumnName();

        // 生成模板
        String sqlTemplate = StrKit.format(GETTER_BY_ID_SQL_TEMPLATE, resultParams, table.value(), primaryName);
        return new TemplateParam(sqlTemplate, new Object[]{pkId}, beanClass);
    }

    /**
     * 根据主键获取映射数据
     *
     * @param beanClass 对象类型
     * @param pkId      主键值
     * @return 映射数据
     */
    public TemplateParam getterByIdSqlTemplate(Class<?> beanClass, Object pkId) {
        Table table = getTable(beanClass);
        // 主键名称
        String primaryName = getPrimaryDescriptor(beanClass).getColumnName();
        // 所有字段映射
        String columns = selectAllColumns(beanClass);
        // 生成模板
        String sqlTemplate = StrKit.format(GETTER_BY_ID_SQL_TEMPLATE, columns, table.value(), primaryName);
        return new TemplateParam(sqlTemplate, new Object[]{pkId}, beanClass);
    }

    /**
     * 根据主键id获取指定属性的值
     *
     * @param beanClass 映射对象类型
     * @param fieldName 字段名称
     * @param pkId      主键值
     * @return 模板参数
     */
    public TemplateParam getColumnValueByIdSqlTemplate(Class<?> beanClass, String fieldName, Object pkId) {
        Table table = getTable(beanClass);
        // 主键描述
        SelectAnnotationDescriptor primaryDescriptor = getPrimaryDescriptor(beanClass);
        // 字段描述
        SelectAnnotationDescriptor fieldDescriptor = getFieldDescriptor(beanClass, fieldName);

        String primaryName = primaryDescriptor.getColumnName();
        String columnName = fieldDescriptor.getColumnName();

        // 生成模板
        String sqlTemplate = StrKit.format(GETTER_BY_ID_SQL_TEMPLATE, columnName, table.value(), primaryName);
        return new TemplateParam(sqlTemplate, new Object[]{pkId}, fieldDescriptor.getFieldClass());
    }


    /**
     * 获得多个字段条件参数
     *
     * @param beanClass        映射对象类型
     * @param conditionalField 条件字段
     * @return 映射结果
     */
    private String getMultipleFieldConditionalParams(Class<?> beanClass, String[] conditionalField) {
        // 多个字段映射
        StringBuilder builder = new StringBuilder();
        Map<String, SelectAnnotationDescriptor> columnDescriptor = getColumnDescriptor(beanClass);
        for (String fieldName : conditionalField) {
            SelectAnnotationDescriptor descriptor = columnDescriptor.get(fieldName);
            if (descriptor == null)
                throw new ToolBoxException("对象【{}】属性【{}】不存在或注释使用错误，未能找到相关描述....", beanClass, fieldName);
            conditionalPackage(builder, descriptor.getColumnName());
        }
        builder.delete(0, CONDITIONAL_COMMA_LENGTH);
        return builder.toString();
    }

    private final Map<Class<?>, String> selectAllColumnsMapping;

    /**
     * 当前bean映射的所有字段
     *
     * @param beanClass 对象类型
     * @return 查询结果字段
     */
    private String selectAllColumns(Class<?> beanClass) {
        return selectAllColumnsMapping.computeIfAbsent(beanClass, k -> {
            StringBuilder builder = new StringBuilder();
            Map<String, SelectAnnotationDescriptor> columnDescriptor = getColumnDescriptor(k);
            for (Map.Entry<String, SelectAnnotationDescriptor> entry : columnDescriptor.entrySet()) {
                SelectAnnotationDescriptor descriptor = entry.getValue();
                columnPackage(builder, descriptor.getColumnName());
            }
            builder.delete(0, COMMA_LENGTH);
            return builder.toString();
        });
    }

    /**
     * 统一列条件封装
     *
     * @param builder    StringBuilder
     * @param columnName 属性名称
     */
    private void conditionalPackage(StringBuilder builder, String columnName) {
        builder.append(CONDITIONAL_COMMA)
                .append(ANTI_SINGLE_QUOTE)
                .append(columnName)
                .append(ANTI_SINGLE_QUOTE)
                .append(CONDITIONAL);
    }

    /**
     * 获得多个字段结果参数
     *
     * @param beanClass  映射对象类型
     * @param fieldNames 多个字段名称
     * @return 映射结果
     */
    private String getMultipleFieldResultParams(Class<?> beanClass, String[] fieldNames) {
        // 多个字段映射
        StringBuilder builder = new StringBuilder();
        Map<String, SelectAnnotationDescriptor> columnDescriptor = getColumnDescriptor(beanClass);
        for (String fieldName : fieldNames) {
            SelectAnnotationDescriptor descriptor = columnDescriptor.get(fieldName);
            if (descriptor == null) continue;
            columnPackage(builder, descriptor.getColumnName());
        }
        builder.delete(0, COMMA_LENGTH);
        return builder.toString();
    }

    /**
     * 统一列名称封装
     *
     * @param builder    StringBuilder
     * @param columnName 属性名称
     */
    private void columnPackage(StringBuilder builder, String columnName) {
        builder.append(COMMA)
                .append(ANTI_SINGLE_QUOTE)
                .append(columnName)
                .append(ANTI_SINGLE_QUOTE);
    }

    @Override
    boolean hasContinue(Field field) {
        return field.getAnnotation(Column.class) == null;
    }

    @Override
    Map<String, SelectAnnotationDescriptor> fieldCache() {
        return new HashMap<>();
    }

    @Override
    void columnDescriptor(Map<String, SelectAnnotationDescriptor> fieldCache, Field field) {
        SelectAnnotationDescriptor descriptor = new SelectAnnotationDescriptor();
        descriptor.setColumnName(field.getAnnotation(Column.class).value());
        descriptor.setPrimaryKey(field.getAnnotation(PkId.class) != null);
        descriptor.setFieldClass(field.getType());
        fieldCache.put(field.getName(), descriptor);
    }

    /**
     * 获取对象属性描述
     *
     * @param beanClass 对象
     * @param fieldName 属性名称
     * @return 属性描述
     */
    private SelectAnnotationDescriptor getFieldDescriptor(Class<?> beanClass, String fieldName) {
        SelectAnnotationDescriptor fieldDescriptor = getColumnDescriptor(beanClass).get(fieldName);
        if (fieldDescriptor != null) return fieldDescriptor;
        throw new ToolBoxException("【{}】的属性【{}】未定义列【{}】注解...", beanClass, fieldName, Column.class);
    }

    /**
     * bean属性上的注解缓存， Map<bean类型，Map<指定类型,指定类型的字段名称>>
     * <p>
     * 类型: pkId -> 主键
     */
    private final Map<Class<?>, Map<String, SelectAnnotationDescriptor>> annotationFieldName;

    /**
     * 获取指定对象类型的主键字段描述
     *
     * @param beanClass 指定对象类型
     * @return 主键字段描述
     */
    private SelectAnnotationDescriptor getPrimaryDescriptor(Class<?> beanClass) {
        return annotationFieldName.computeIfAbsent(beanClass, k -> new ConcurrentHashMap<>())
                .computeIfAbsent("pkId", id -> {
                    Map<String, SelectAnnotationDescriptor> columnDescriptor = getColumnDescriptor(beanClass);
                    for (Map.Entry<String, SelectAnnotationDescriptor> entry : columnDescriptor.entrySet()) {
                        SelectAnnotationDescriptor descriptor = entry.getValue();

                        if (descriptor.getPrimaryKey())
                            return descriptor;
                    }
                    throw new ToolBoxException("【{}】未定义主键【{}】注解或未定义列【{}】注解...", beanClass, PkId.class, Column.class);
                });
    }

    /**
     * 对象映射缓存
     * <p>
     * key->对象类型， value->( key->数据表列名  value->对象属性名称 )
     */
    private final Map<Class<?>, Map<String, String>> beanPropertyMapping;

    /**
     * 获取bean属性与数据表列名称映射 ; key->数据表列名  value->对象属性名称
     *
     * @param beanClass bean类型
     * @return 映射集
     */
    public Map<String, String> getPropertyMapping(Class<?> beanClass) {
        return beanPropertyMapping.computeIfAbsent(beanClass, k -> {
            Map<String, String> propertyMapping = new HashMap<>();
            // 不是javaBean对象返回空的
            if (!BeanKit.isBean(k)) return propertyMapping;

            Map<String, SelectAnnotationDescriptor> columnDescriptor = getColumnDescriptor(beanClass);
            for (Map.Entry<String, SelectAnnotationDescriptor> entry : columnDescriptor.entrySet()) {
                String fieldName = entry.getKey();
                String columnName = entry.getValue().getColumnName();

                // 冲突判断
                if (propertyMapping.containsKey(columnName)) {
                    String conflictingName = propertyMapping.get(columnName);
                    throw new ToolBoxException("在映射对象【{}】中属性为【{}】映射的数据表列名【{}】与属性为【{}】映射的数据表列名【{}】发生冲突...",
                            beanClass, fieldName, columnName, conflictingName, columnName);
                }
                propertyMapping.put(columnName, fieldName);
            }
            return propertyMapping;
        });
    }

    /*---------------------------饿汉式单例*/
    private static final SelectSqlTemplate selectSqlTemplate;

    static {
        selectSqlTemplate = new SelectSqlTemplate();
    }

    private SelectSqlTemplate() {
        annotationFieldName = new ConcurrentHashMap<>();
        beanPropertyMapping = new ConcurrentHashMap<>();
        selectAllColumnsMapping = new ConcurrentHashMap<>();
    }

    public static SelectSqlTemplate getInstance() {
        return selectSqlTemplate;
    }

}
